//=============================================================================
// ProMesh.NET - .NET Web Application Framework 
//
// Copyright (c) 2003-2008 Philippe Leybaert
//
// Permission is hereby granted, free of charge, to any person obtaining a copy 
// of this software and associated documentation files (the "Software"), to deal 
// in the Software without restriction, including without limitation the rights 
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
// copies of the Software, and to permit persons to whom the Software is 
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in 
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//=============================================================================

using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using Activa.LazyParser;

namespace Activa.ProMesh
{
    public class ViewDataContainer : IDictionary<string, object>, IDynamicObject
    {
        private readonly Dictionary<string, object> _dic = new Dictionary<string, object>(StringComparer.InvariantCultureIgnoreCase);
        private readonly LinkedList<object> _objects = new LinkedList<object>();

        public ViewDataContainer()
        {
        }

        public ViewDataContainer(params object[] dataObjects)
        {
            Apply(dataObjects);
        }

        public Table AddTable(string name)
        {
            Table table = new Table();

            this[name] = table;

            return table;
        }
			
        [Obsolete("Use Apply()")]
        public void Merge(ViewDataContainer row)
        {
            foreach (string s in row.Keys)
                this[s] = row[s];
        }

        public void Apply(ViewDataContainer source)
        {
            foreach (string s in source._dic.Keys)
                _dic[s] = source._dic[s];

            foreach (object obj in source._objects)
                _objects.AddLast(obj);
        }

        public void Apply(object obj)
        {
            if (obj is ViewDataContainer)
                Apply((ViewDataContainer)obj);
            else
                _objects.AddLast(obj);
        }

        public void Apply(params object[] objects)
        {
            foreach (object obj in objects)
                Apply(obj);
        }

        public ViewDataContainer Clone()
        {
            ViewDataContainer newContainer = (ViewDataContainer) Activator.CreateInstance(GetType());

            newContainer.Apply(this);

            return newContainer;
        }

        private bool HasProperty(string key)
        {
            if (_objects.Count == 0)
                return false;

            foreach (object obj in _objects)
            {
                if (obj.GetType().GetProperty(key, BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy) != null)
                    return true;
            }

            return false;
        }

        public bool ContainsKey(string key)
        {
            return _dic.ContainsKey(key) || HasProperty(key);
        }

        public void Add(string key, object value)
        {
            _dic.Add(key,value);
        }

        public bool Remove(string key)
        {
            return _dic.Remove(key);
        }

        public bool TryGetValue(string key, out object value)
        {
            if (_dic.TryGetValue(key, out value))
                return true;

            value = GetDataObjectProperty(key);

            return true;
        }

        private object GetDataObjectProperty(string key)
        {
            if (_objects.Count == 0)
                return null;

            foreach (object dataObject in _objects)
            {
                MemberInfo[] members = dataObject.GetType().GetMember(key);

                if (members.Length == 0)
                    continue;

                MemberInfo member = members[0];

                if (members.Length > 1) // CoolStorage, ActiveRecord and Dynamic Proxy frameworks sometimes return > 1 member
                {
                    foreach (MemberInfo mi in members)
                        if (mi.DeclaringType == dataObject.GetType())
                            member = mi;
                }

                if (member is PropertyInfo)
                {
                    return ((PropertyInfo) member).GetValue(dataObject, null);
                }

                if (member is FieldInfo)
                {
                    return ((FieldInfo) member).GetValue(dataObject);
                }
            }

            return null;
        }

        public bool TryGetValue(string propertyName, out object value, out Type type)
        {
            type = typeof(object);

            if (_dic.TryGetValue(propertyName, out value))
            {
                if (value == null)
                    return true;

                type = value.GetType();

                return true;
            }

            if (_objects.Count == 0)
                return false;

            foreach (object dataObject in _objects)
            {
                MemberInfo[] members = dataObject.GetType().GetMember(propertyName);

                if (members.Length == 0)
                    continue;

                MemberInfo member = members[0];

                if (members.Length > 1) // CoolStorage, ActiveRecord and Dynamic Proxy frameworks sometimes return > 1 member
                {
                    foreach (MemberInfo mi in members)
                        if (mi.DeclaringType == dataObject.GetType())
                            member = mi;
                }

                if (member is PropertyInfo)
                {
                    value = ((PropertyInfo) member).GetValue(dataObject, null);
                    type = ((PropertyInfo) member).PropertyType;

                    return true;
                }

                if (member is FieldInfo)
                {
                    value = ((FieldInfo) member).GetValue(dataObject);
                    type = ((FieldInfo) member).FieldType;

                    return true;
                }
            }

            return false;
        }

        public object this[string key]
        {
            get
            {
                object value;

                if (TryGetValue(key, out value))
                    return value;

                return null;
            }
            set
            {
                _dic[key] = value;
            }
        }

        public void AddType<T>(string name)
        {
            this[name] = ContextFactory.CreateType(typeof (T));
        }

        public void AddType<T>()
        {
            this[typeof(T).Name] = ContextFactory.CreateType(typeof(T));
        }

        public void AddFunction<T>(string name, string functionName)
        {
            this[name] = ContextFactory.CreateFunction(typeof (T), functionName);
        }

        public void AddFunction<T>(string name, string functionName, T targetObject)
        {
            this[name] = ContextFactory.CreateFunction(typeof(T), functionName, targetObject);
        }

        public void AddFunction(string name, Type type, string functionName)
        {
            this[name] = ContextFactory.CreateFunction(type, functionName);
        }

        public void AddFunction(string name, Type type, string functionName, object targetObject)
        {
            this[name] = ContextFactory.CreateFunction(type, functionName, targetObject);
        }

        public void AddFunction(string name, MethodInfo methodInfo)
        {
            this[name] = ContextFactory.CreateFunction(methodInfo);
        }

        public void AddFunction(string name, MethodInfo methodInfo, object targetObject)
        {
            this[name] = ContextFactory.CreateFunction(methodInfo,targetObject);
        }

        public ICollection<string> Keys
        {
            get { return _dic.Keys; }
        }

        public ICollection<object> Values
        {
            get { return _dic.Values; }
        }

        void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
        {
            ((ICollection<KeyValuePair<string, object>>)_dic).Add(item);
        }

        public void Clear()
        {
            _dic.Clear();
            _objects.Clear();
        }

        bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
        {
            return ((ICollection<KeyValuePair<string, object>>) _dic).Contains(item);
        }

        void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
        {
            ((ICollection<KeyValuePair<string, object>>)_dic).CopyTo(array,arrayIndex);
        }

        bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
        {
            return ((ICollection<KeyValuePair<string, object>>) _dic).Remove(item);
        }

        public int Count
        {
            get { return _dic.Count; }
        }

        bool ICollection<KeyValuePair<string, object>>.IsReadOnly
        {
            get { return ((ICollection<KeyValuePair<string, object>>) _dic).IsReadOnly; }
        }

        IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator()
        {
            throw new NotImplementedException();
        }

        public IEnumerator GetEnumerator()
        {
            return ((IEnumerable<KeyValuePair<string, object>>) this).GetEnumerator();
        }
    }
}